001    /*
002     * Copyright 2006 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.metro.builder;
020    
021    import java.io.IOException;
022    import java.net.URI;
023    
024    import net.dpml.component.ActivationPolicy;
025    
026    import net.dpml.metro.data.ContextDirective;
027    import net.dpml.metro.data.CategoryDirective;
028    import net.dpml.metro.data.CategoriesDirective;
029    import net.dpml.metro.data.ComponentDirective;
030    import net.dpml.metro.data.ValueDirective;
031    import net.dpml.metro.data.LookupDirective;
032    
033    import net.dpml.metro.info.LifestylePolicy;
034    import net.dpml.metro.info.CollectionPolicy;
035    import net.dpml.metro.info.PartReference;
036    import net.dpml.metro.info.Priority;
037    
038    import net.dpml.lang.ValueDecoder;
039    import net.dpml.lang.Value;
040    
041    import net.dpml.util.Resolver;
042    import net.dpml.util.DOM3DocumentBuilder;
043    import net.dpml.util.ElementHelper;
044    import net.dpml.util.DecodingException;
045    import net.dpml.util.SimpleResolver;
046    
047    import org.w3c.dom.Document;
048    import org.w3c.dom.Element;
049    
050    /**
051     * Construct a state graph.
052     */
053    public class ComponentDecoder
054    {
055        private static final String STATE_SCHEMA_URN = "link:xsd:dpml/lang/dpml-state#1.0";
056        
057        private static final String SCHEMA_URN = "link:xsd:dpml/lang/dpml-component#1.0";
058        
059        private static final DOM3DocumentBuilder DOCUMENT_BUILDER = new DOM3DocumentBuilder();
060        
061        private static final ComponentTypeDecoder TYPE_DECODER = new ComponentTypeDecoder();
062        
063        private static final ValueDecoder VALUE_DECODER = new ValueDecoder();
064        
065       /**
066        * Construct a component directive using the supplied uri. The uri
067        * must refer to an XML document containing a root component element
068        * (typically used in component data testcases).
069        *
070        * @param uri the part uri
071        * @return the component directive
072        * @exception IOException if an error occurs during directive creation
073        */
074        public ComponentDirective loadComponentDirective( URI uri ) throws IOException
075        {
076            if( null == uri )
077            {
078                throw new NullPointerException( "uri" );
079            }
080            try
081            {
082                final Document document = DOCUMENT_BUILDER.parse( uri );
083                final Element root = document.getDocumentElement();
084                Resolver resolver = new SimpleResolver();
085                return buildComponent( root, resolver );
086            }
087            catch( Throwable e )
088            {
089                final String error =
090                  "An error while attempting to load a component directive."
091                  + "\nURI: " + uri;
092                IOException exception = new IOException( error );
093                exception.initCause( e );
094                throw exception;
095            }
096        }
097        
098       /**
099        * Construct a component directive using the supplied DOM element.
100        * @param root the element representing the component directive definition
101        * @param resolver build-time uri resolver
102        * @return the component directive
103        * @exception DecodingException if an error occurs during directive creation
104        */
105        public ComponentDirective buildComponent( Element root, Resolver resolver ) throws DecodingException
106        {
107            if( null == root )
108            {
109                throw new NullPointerException( "root" );
110            }
111            String tag = root.getTagName();
112            if( "component".equals( tag ) )
113            {
114                return createComponentDirective( root, resolver );
115            }
116            else
117            {
118                final String error = 
119                  "Component directive element name [" 
120                  + tag 
121                  + "] is not recognized.";
122                throw new DecodingException( root, error );
123            }
124        }
125        
126        private ComponentDirective createComponentDirective( 
127          Element element, Resolver resolver ) throws DecodingException
128        {
129            String classname = buildComponentClassname( element );
130            String name = buildComponentName( element );
131            ActivationPolicy activation = buildActivationPolicy( element );
132            CollectionPolicy collection = buildCollectionPolicy( element );
133            LifestylePolicy lifestyle = buildLifestylePolicy( element );
134            CategoriesDirective categories = getNestedCategoriesDirective( element );
135            ContextDirective context = getNestedContextDirective( element );
136            PartReference[] parts = getNestedParts( element, resolver );
137            URI base = getBaseURI( element, resolver );
138            
139            if( null == base )
140            {
141                if( null == classname )
142                {
143                    final String error = 
144                      "Missing component type attribute.";
145                    throw new DecodingException( element, error );
146                }
147            }
148            else
149            {
150                if( null != classname )
151                {
152                    final String error = 
153                      "llegal attempt to override a base type in a supertype.";
154                    throw new DecodingException( element, error );
155                }
156            }
157            
158            try
159            {
160                return new ComponentDirective( 
161                  name, activation, collection, lifestyle, classname, 
162                  categories, context, parts, base );
163            }
164            catch( Exception e )
165            {
166                final String error = 
167                  "Component directive creation error.";
168                throw new DecodingException( element, error, e );
169            }
170        }
171        
172        private URI getBaseURI( Element element, Resolver resolver ) throws DecodingException
173        {
174            String base = ElementHelper.getAttribute( element, "uri" );
175            if( null == base )
176            {
177                return null;
178            }
179            else
180            {
181                try
182                {
183                    return resolver.toURI( base );
184                }
185                catch( Exception e )
186                {
187                    final String error = 
188                      "Error resolving 'uri' attribute value: " 
189                      + base;
190                    throw new DecodingException( element, error, e );
191                }
192            }
193        }
194        
195        private String buildComponentClassname( Element element ) throws DecodingException
196        {
197            return ElementHelper.getAttribute( element, "type" );
198        }
199        
200        private ActivationPolicy buildActivationPolicy( Element element ) throws DecodingException
201        {
202            String policy = ElementHelper.getAttribute( element, "activation" );
203            if( null == policy )
204            {
205                return null;
206            }
207            else
208            {
209                return ActivationPolicy.parse( policy );
210            }
211        }
212        
213        private LifestylePolicy buildLifestylePolicy( Element element ) throws DecodingException
214        {
215            String policy = ElementHelper.getAttribute( element, "lifestyle", null );
216            if( null != policy )
217            { 
218                return LifestylePolicy.parse( policy );
219            }
220            else
221            {
222                return null;
223            }
224        }
225        
226        private CollectionPolicy buildCollectionPolicy( Element element ) throws DecodingException
227        {
228            String policy = ElementHelper.getAttribute( element, "collection" );
229            if( null != policy )
230            { 
231                return CollectionPolicy.parse( policy );
232            }
233            else
234            {
235                return null;
236            }
237        }
238        
239        private String buildComponentName( Element element )
240        {
241            return ElementHelper.getAttribute( element, "name" );
242        }
243        
244        private CategoriesDirective getNestedCategoriesDirective( Element root )
245        {
246            Element element = ElementHelper.getChild( root, "categories" );
247            if( null == element )
248            {
249                return null;
250            }
251            else
252            {
253                return createCategoriesDirective( element );
254            }
255        }
256        
257        private CategoriesDirective createCategoriesDirective( Element element )
258        {
259            if( null == element )
260            {
261                return null;
262            }
263            else
264            {
265                String name = ElementHelper.getAttribute( element, "name" );
266                Priority priority = createPriority( element );
267                String target = ElementHelper.getAttribute( element, "target" );
268                CategoryDirective[] categories = createCategoryDirectiveArray( element );
269                return new CategoriesDirective( name, priority, target, categories );
270            }
271        }
272        
273        private CategoryDirective createCategoryDirective( Element element )
274        {
275            String name = ElementHelper.getAttribute( element, "name" );
276            Priority priority = createPriority( element );
277            String target = ElementHelper.getAttribute( element, "target" );
278            return new CategoryDirective( name, priority, target );
279        }
280        
281        private CategoryDirective[] createCategoryDirectiveArray( Element element )
282        {
283            Element[] children = ElementHelper.getChildren( element );
284            CategoryDirective[] categories = new CategoryDirective[ children.length ];
285            for( int i=0; i<categories.length; i++ )
286            {
287                Element elem = children[i];
288                if( "category".equals( elem.getTagName() ) )
289                {
290                    categories[i] = createCategoryDirective( elem );
291                }
292                else
293                {
294                    categories[i] = createCategoriesDirective( elem );
295                }
296            }
297            return categories;
298        }
299        
300        private Priority createPriority( Element element )
301        {
302            String priority = ElementHelper.getAttribute( element, "priority" );
303            if( null == priority )
304            {
305                return null;
306            }
307            else
308            {
309                return Priority.parse( priority );
310            }
311        }
312        
313        private ContextDirective getNestedContextDirective( Element root ) throws DecodingException
314        {
315            Element context = ElementHelper.getChild( root, "context" );
316            if( null == context )
317            {
318                return null;
319            }
320            else
321            {
322                return createContextDirective( context );
323            }
324        }
325        
326        private ContextDirective createContextDirective( Element element ) throws DecodingException
327        {
328            String classname = ElementHelper.getAttribute( element, "class" );
329            Element[] children = ElementHelper.getChildren( element );
330            PartReference[] entries = new PartReference[ children.length ];
331            for( int i=0; i<children.length; i++ )
332            {
333                Element elem = children[i];
334                entries[i] = createContextEntryPartReference( elem );
335            }
336            return new ContextDirective( classname, entries );
337        }
338        
339        private PartReference createContextEntryPartReference( Element element ) throws DecodingException
340        {
341            String key = ElementHelper.getAttribute( element, "key" );
342            String spec = ElementHelper.getAttribute( element, "lookup" );
343            if( null != spec )
344            {
345                LookupDirective directive = new LookupDirective( spec );
346                return new PartReference( key, directive );
347            }
348            else
349            {
350                String name = element.getTagName();
351                if( "entry".equals( name ) )
352                {
353                    ValueDirective directive = buildValueDirective( element );
354                    return new PartReference( key, directive );
355                }
356                //else if( "component".equals( name ) )
357                //{
358                //    ComponentDirective directive = buildComponent( element );
359                //    return new PartReference( key, directive );
360                //}
361                else
362                {
363                    final String error = 
364                      "Context entry element is not recognized.";
365                    throw new DecodingException( element, error );
366                }
367            }
368        }
369        
370       /**
371        * Build a value directive using a supplied DOM element.
372        * @param element the DOM element
373        * @return the value directive
374        */
375        protected ValueDirective buildValueDirective( Element element )
376        {
377            String classname = ElementHelper.getAttribute( element, "class" );
378            String method = ElementHelper.getAttribute( element, "method" );
379            Element[] elements = ElementHelper.getChildren( element, "param" );
380            if( elements.length > 0 )
381            {
382                Value[] values = VALUE_DECODER.decodeValues( elements );
383                return new ValueDirective( classname, method, values );
384            }
385            else
386            {
387                String value = ElementHelper.getAttribute( element, "value" );
388                return new ValueDirective( classname, method, value );
389            }
390        }
391        
392        private PartReference[] getNestedParts( Element root, Resolver resolver ) throws DecodingException
393        {
394            Element parts = ElementHelper.getChild( root, "parts" );
395            if( null == parts )
396            {
397                return null;
398            }
399            else
400            {
401                return createParts( parts, resolver );
402            }
403        }
404        
405        private PartReference[] createParts( Element element, Resolver resolver ) throws DecodingException
406        {
407            Element[] children = ElementHelper.getChildren( element );
408            PartReference[] parts = new PartReference[ children.length ];
409            for( int i=0; i<children.length; i++ )
410            {
411                Element elem = children[i];
412                parts[i] = createPartReference( elem, resolver );
413            }
414            return parts;
415        }
416        
417        private PartReference createPartReference( Element element, Resolver resolver ) throws DecodingException
418        {
419            String tag = element.getTagName();
420            String key = ElementHelper.getAttribute( element, "key" );
421            int priority = getPriority( element );
422            if( "component".equals( tag ) )
423            {
424                ComponentDirective directive = buildComponent( element, resolver );
425                return new PartReference( key, directive, priority );
426            }
427            else
428            {
429                final String error = 
430                  "Component part element name [" 
431                  + tag 
432                  + "] is not recognized.";
433                throw new DecodingException( element, error );
434            }
435        }
436        
437        private int getPriority( Element element ) throws DecodingException
438        {
439            String priority = ElementHelper.getAttribute( element, "priority", "0" );
440            try
441            {
442                return Integer.parseInt( priority );
443            }
444            catch( Exception e )
445            {
446                final String error = 
447                  "Unable to parse priority value.";
448                throw new DecodingException( element, error, e );
449            }
450        }
451        
452       /**
453        * Internal utility to get the name of the class without the package name. Used
454        * when constructing a default component name.
455        * @param classname the fully qualified classname
456        * @return the short class name without the package name
457        */
458        private String toName( String classname )
459        {
460            int i = classname.lastIndexOf( "." );
461            if( i == -1 )
462            {
463                return classname.toLowerCase();
464            }
465            else
466            {
467                return classname.substring( i + 1, classname.length() ).toLowerCase();
468            }
469        }
470    
471    }